package biz

import (
	"context"
	"time"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/nbutton23/zxcvbn-go"
	"gorm.io/gorm"

	pb "gitee.com/danqingzhao/go-dango/api/user/customer/v1"
	"gitee.com/danqingzhao/go-dango/pkg/common/core"
	"gitee.com/danqingzhao/go-dango/pkg/proto/auth"
	"gitee.com/danqingzhao/go-dango/pkg/proto/settings"
	"gitee.com/danqingzhao/go-dango/pkg/third/orm"
	"gitee.com/danqingzhao/go-dango/pkg/utils"
)

type CustomerModel struct {
	Id       uint32         `gorm:"column:id;primary_key;AUTO_INCREMENT;comment:编号" json:"id"`
	Username string         `gorm:"column:username;type:varchar(50);not null;uniqueIndex;comment:用户名" json:"username"`
	Password string         `gorm:"column:password;type:varchar(150);not null;comment:密码" json:"password"`
	DataJoin time.Time      `gorm:"column:data_join;autoCreateTime;comment:加入时间" json:"data_join"`
	DeleteAt gorm.DeletedAt `gorm:"column:delete_at;type:boolean;comment:是否删除" json:"delete_at"`
	IsActive bool           `gorm:"column:is_active;type:boolean;comment:是否激活" json:"is_active"`
	IsStaff  bool           `gorm:"column:is_staff;type:boolean;comment:是否是工作人员" json:"is_staff"`
	Remark   string         `gorm:"column:remark;type:varchar(254);comment:备注" json:"remark"`
	RoleId   uint32         `gorm:"column:role_id;foreignKey:RoleId;references:Id;not null;constraint:OnDelete:CASCADE;comment:角色" json:"role"`
	Role     RoleModel      `gorm:"foreignKey:RoleId;constraint:OnDelete:CASCADE"`
}

func (m *CustomerModel) TableName() string {
	return "user_customer"
}

type CustomerRepo interface {
	CreateModel(context.Context, *CustomerModel) error
	UpdateModel(context.Context, CustomerModel, ...any) error
	DeleteModel(context.Context, ...any) error
	FindModel(context.Context, []string, ...any) (*CustomerModel, error)
	ListModel(context.Context, orm.QueryParams) (int64, []CustomerModel, error)
	GenerateToken(string, auth.ExtendClaims) (string, error)
}

type CustomerUsecase struct {
	log      *log.Helper
	roleRepo RoleRepo
	custRepo CustomerRepo
}

func NewCustomerUsecase(
	logHelpers *settings.LogHelpers,
	roleRepo RoleRepo,
	custRepo CustomerRepo,
) *CustomerUsecase {
	return &CustomerUsecase{
		log:      logHelpers.Biz,
		roleRepo: roleRepo,
		custRepo: custRepo,
	}
}

func (uc *CustomerUsecase) assoRole(
	ctx context.Context,
	roleId uint32,
) (*RoleModel, *errors.Error) {
	rm, fErr := uc.roleRepo.FindModel(ctx, nil, roleId)
	if fErr != nil {
		rErr := core.NewGormError(fErr, pb.ErrorReason_CUSTOMER_FIND_ROLE_ERROR.String(), core.GenerateTempDataByID(roleId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return rm, nil
}

func (uc *CustomerUsecase) CreateCustomer(
	ctx context.Context,
	m CustomerModel,
	isEncty bool,
) (*CustomerModel, *errors.Error) {
	result := zxcvbn.PasswordStrength(m.Password, []string{m.Username})
	if result.Score < 3 {
		rErr := errors.BadRequest(pb.ErrorReason_CUSTOMER_PASSWORD_TOO_WEAK.String(), "password strength is too weak")
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if isEncty {
		m.Password = utils.SM4cbcEncrypt(m.Password)
	}
	rm, aErr := uc.assoRole(ctx, m.RoleId)
	if aErr != nil {
		return nil, aErr
	}
	m.Role = *rm
	if err := uc.custRepo.CreateModel(ctx, &m); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_CUSTOMER_CREATE_FAILED.String(), core.GenerateTempDataByID(m.RoleId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return &m, nil
}

func (uc *CustomerUsecase) UpdateCustomerById(
	ctx context.Context,
	custId uint32,
	m CustomerModel,
	isEncty bool,
) (*CustomerModel, *errors.Error) {
	result := zxcvbn.PasswordStrength(m.Password, []string{m.Username})
	if result.Score < 3 {
		rErr := errors.BadRequest(pb.ErrorReason_CUSTOMER_PASSWORD_TOO_WEAK.String(), "password strength is too weak")
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if m.Password != "" && isEncty {
		m.Password = utils.SM4cbcEncrypt(m.Password)
	}
	if m.RoleId != 0 {
		rm, aErr := uc.assoRole(ctx, m.RoleId)
		if aErr != nil {
			return nil, aErr
		}
		m.Role = *rm
	}
	if err := uc.custRepo.UpdateModel(ctx, m, "id = ?", custId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_CUSTOMER_UPDATE_FAILED.String(), core.GenerateTempDataByID(custId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return uc.FindCustomerByID(ctx, custId)
}

func (uc *CustomerUsecase) DeleteCustomerById(ctx context.Context, custId uint32) *errors.Error {
	if err := uc.custRepo.DeleteModel(ctx, custId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_CUSTOMER_DELETE_FAILED.String(), core.GenerateTempDataByID(custId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return rErr
	}
	return nil
}

func (uc *CustomerUsecase) FindCustomerByID(ctx context.Context, custId uint32) (*CustomerModel, *errors.Error) {
	m, err := uc.custRepo.FindModel(ctx, []string{"Role"}, custId)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_CUSTOMER_FIND_FAILED.String(), core.GenerateTempDataByID(custId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return m, nil
}

func (uc *CustomerUsecase) FindCustomerByName(ctx context.Context, username string) (*CustomerModel, *errors.Error) {
	m, err := uc.custRepo.FindModel(ctx, []string{"Role"}, map[string]any{"username": username})
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_CUSTOMER_FIND_FAILED.String(), map[string]string{"username": username})
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return m, nil
}

func (uc *CustomerUsecase) ListCustomer(
	ctx context.Context,
	page, size int,
	query map[string]any,
) (int64, []CustomerModel, *errors.Error) {
	qp := orm.QueryParams{
		Preloads: []string{"Role"},
		Query:    query,
		OrderBy:  []string{"id"},
		Limit:    max(size, 0),
		Offset:   max(page-1, 0),
		IsCount:  true,
	}
	count, ms, err := uc.custRepo.ListModel(ctx, qp)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_CUSTOMER_LIST_FAILED.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return 0, nil, rErr
	}
	return count, ms, nil
}

func (uc *CustomerUsecase) CheckCustomerPassword(
	ctx context.Context,
	custId uint32,
	password string,
	isEncry bool,
) (bool, *errors.Error) {
	m, fErr := uc.custRepo.FindModel(ctx, nil, custId)
	if fErr != nil {
		rErr := core.NewGormError(fErr, pb.ErrorReason_CUSTOMER_FIND_FAILED.String(), core.GenerateTempDataByID(custId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return false, rErr
	}
	if !isEncry {
		password = utils.SM4cbcEncrypt(password)
	}
	return password == m.Password, nil
}

func (uc *CustomerUsecase) GeneAccessToken(ctx context.Context, ecs auth.ExtendClaims) (string, *errors.Error) {
	token, gErr := uc.custRepo.GenerateToken(auth.JWTSubject_ACCESS.String(), ecs)
	if gErr != nil {
		rErr := errors.InternalServer(pb.ErrorReason_CUSTOMER_GENERATE_TOKEN_ERROR.String(), gErr.Error())
		uc.log.WithContext(ctx).Error(rErr.Error())
		return "", rErr
	}
	return token, nil
}

func (uc *CustomerUsecase) GeneRefreshToken(ctx context.Context, ecs auth.ExtendClaims) (string, *errors.Error) {
	token, gErr := uc.custRepo.GenerateToken(auth.JWTSubject_REFRESH.String(), ecs)
	if gErr != nil {
		rErr := errors.InternalServer(pb.ErrorReason_CUSTOMER_GENERATE_TOKEN_ERROR.String(), gErr.Error())
		uc.log.WithContext(ctx).Error(rErr.Error())
		return "", rErr
	}
	return token, nil
}
